// Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.

// 删除vm任务
package asynctask

import (
	"crypto/tls"
	"encoding/json"
	"scase.io/application-auto-scaling-service/pkg/common"
	event "scase.io/application-auto-scaling-service/pkg/service/event"

	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/pkg/errors"
	"scase.io/application-auto-scaling-service/pkg/api/model"
	"scase.io/application-auto-scaling-service/pkg/cloudresource"
	"scase.io/application-auto-scaling-service/pkg/db"
	"scase.io/application-auto-scaling-service/pkg/setting"
	"scase.io/application-auto-scaling-service/pkg/utils/logger"
)

const (
	TaskTypeDelVmTask = db.TaskTypeDeleteVm
	startCleanUp      = "https://%s:60001/v1/cleanup"
	shutdownCode      = 202

	shutdownStatePattern  = "https://%s:60001/v1/cleanup-state"
	shutdownStateFinished = "FINISHED"
	sleepSecond           = 10
	ecsNotFoundErrorCode  = "Ecs.0114"
	EcsStateDeleted       = "DELETED"
	EcsStateActive        = "ACTIVE"
	defaultMaxRetry       = 10
)

var (
	ErrEcsNotFound  = errors.New("ecs not found")
	ErrEcsNotActive = errors.New("ecs not active")
)

// DelVmTask vm删除任务，先关机，后删除
type DelVmTask struct {
	vmId      string
	projectId string
	groupId   string
	BaseTask
	event *event.EventService
}

type CleanUpState struct {
	State string `json:"state"`
}

// NewDelVmTask ...
func NewDelVmTask(vmId string, projectId string, groupId string) *DelVmTask {
	return &DelVmTask{
		vmId:      vmId,
		projectId: projectId,
		groupId:   groupId,
	}
}

// GetKey get id of the resource corresponding to the task
func (t *DelVmTask) GetKey() string {
	return t.vmId
}

// GetType get task type
func (t *DelVmTask) GetType() string {
	return TaskTypeDelVmTask
}

// Run run task
func (t *DelVmTask) Run(log *logger.FMLogger) error {
	// 任务重试次数达到最大重试次数，应当终止该任务
	taskRetryTimes := t.GetRetryTimes()
	if taskRetryTimes > common.AsyncMaxRetryTimes {
		return t.handlerTaskComplete(log)
	}
	eventService, err := event.NewEventService(t.projectId, nil, log)
	if err != nil {
		log.Error("new event service error:%+v", err)
		return err
	}
	t.event = eventService
	resCtrl, err := cloudresource.GetResourceController(t.projectId)
	if err != nil {
		return err
	}
	IP, err := t.hanlerEcsInfo(resCtrl, log)
	if err != nil {
		// ecs已经被删除或者不存在，删除所有相关的任务信息后退出
		if errors.Is(err, ErrEcsNotActive) || errors.Is(err, ErrEcsNotFound) {
			log.Warn("esc not active or not found:%+v, quit this task", err)
			return t.handlerTaskComplete(log)
		}
		log.Error("handle ecs info error:%+v", err)
		return err
	}
	//1. 等待auxproxy关闭进程并上报，成功之后直接删除虚拟机
	if err := t.StartCleanUpProcess(IP, log); err != nil {
		log.Error("start cleanup process error:%+v", err)
		eveReq := t.event.BuildInstanceEventReq("[delete-vm] failed", t.vmId, fmt.Sprintf("delete-vm task [%s] failed with error:%+v", t.vmId, err.Error()),
			fmt.Sprintf("delete vm [%s] failed", t.vmId), common.EventTraceStateIncident)
		t.event.RegisterEvent(eveReq, t.projectId, log)
		return err
	}
	//2. 先关机，再删除vm
	if err := t.StopAndDeleteECS(resCtrl, log); err != nil {
		log.Error("stop and delete ecs %s failed:%+v", t.vmId, err)
		eveReq := t.event.BuildInstanceEventReq("[delete-vm] failed", t.vmId, fmt.Sprintf("delete-vm task [%s] failed with error:%+v", t.vmId, err.Error()),
			fmt.Sprintf("delete vm [%s] failed", t.vmId), common.EventTraceStateIncident)
		t.event.RegisterEvent(eveReq, t.projectId, log)
		return err
	}

	// 3. 删除db中记录的vm删除任务
	if err := t.handlerTaskComplete(log); err != nil {
		log.Error("delete vm %s from db error:%+v", t.vmId, err)
		return err
	}
	eveReq := t.event.BuildInstanceEventReq("[delete-vm] success", t.vmId, fmt.Sprintf(" delete vm [%s] success", t.vmId), fmt.Sprintf("delete vm [%s] success", t.vmId), common.EventTraceStateNormal)
	t.event.RegisterEvent(eveReq, t.projectId, log)
	return nil
}

func (t *DelVmTask) hanlerEcsInfo(resCtrl *cloudresource.ResourceController, log *logger.FMLogger) (string, error) {
	ecsInfo, err := resCtrl.GetECSInfo(log, t.vmId)
	if err != nil {
		errMsg := &model.ErrorMsgECS{}
		if errEcs := json.Unmarshal([]byte(err.Error()), errMsg); errEcs != nil {
			log.Error("unmarshal err msg failed:%+v, error:%+v", errEcs, err)
			return "", errEcs
		}
		// ecs 不存在，退出该任务
		if errMsg.ErrorCode == ecsNotFoundErrorCode {
			log.Error("ecs %s not found, error message:%+v", t.vmId, errMsg.ErrorMessage)
			return "", errors.Wrapf(ErrEcsNotFound, "ecs %s not found", t.vmId)
		}
		log.Error("get ecs %s info failed:%+v", t.vmId, err)
		return "", err
	}
	if ecsInfo.Server.Status != EcsStateActive {
		log.Error("ecs %s not active", t.vmId)
		return "", errors.Wrapf(ErrEcsNotActive, "ecs %s not active, now status is %s", t.vmId, ecsInfo.Server.Status)
	}

	IPList, err := resCtrl.ParseECSIP(log, ecsInfo, setting.ConnectToAuxproxyByIP)
	if err != nil {
		log.Error("get IP list failed with error:%s", err.Error())
		return "", err
	}
	// 考虑多网卡情况
	if len(IPList) > 1 {
		log.Info("ecs %s has %d addresses, use IP:%s to connect", t.vmId, len(IPList), IPList[0])
	}
	return IPList[0], nil
}

func (t *DelVmTask) handlerTaskComplete(log *logger.FMLogger) error {
	if err := db.EcsInfoTable().DeleteDeletingVm([]string{t.vmId}); err != nil {
		log.Error("delete vm %s from db error:%+v", t.vmId, err)
		return err
	}
	if err := db.DeleteAsyncTask(t.GetType(), t.GetKey()); err != nil {
		log.Error("delete async task %s error:%+v", t.vmId, err)
		return err
	}
	return nil
}

func (t *DelVmTask) StopAndDeleteECS(resCtrl *cloudresource.ResourceController, log *logger.FMLogger) error {
	if err := resCtrl.BatchStopServers(log, []string{t.vmId}); err != nil {
		log.Error("batch stop servers failed with error:%s", err.Error())
		return err
	}
	if err := resCtrl.WaitVmShutoff(log, t.vmId); err != nil {
		log.Error("wait vm %s shutoff failed with error:%+v", t.vmId, err)
		return err
	}
	if err := resCtrl.DeleteVm(log, t.vmId); err != nil {
		log.Error(" delete vm failed with error:%s", err.Error())
		return err
	}
	log.Info("shutdown and delete vm [%s] success", t.vmId)
	return nil
}

func (t *DelVmTask) StartCleanUpProcess(IP string, log *logger.FMLogger) error {
	protectTime, err := db.GetSessionProtectTimeByGroupId(t.groupId, t.projectId)
	if err != nil {
		log.Error("get protectTime failed with error:%s", err.Error())
		return err
	}
	log.Info("success get protect time is :%d minutes", protectTime)
	maxRetry := protectTime * 60 / sleepSecond
	if maxRetry == 0 {
		maxRetry = defaultMaxRetry
	}

	if err := StartCleanUp(IP, log, defaultMaxRetry); err != nil {
		log.Error("start clean up to IP %s error:%+v", IP, err)
		return err
	}
	if err := WaitAuxproxy(IP, log, maxRetry); err != nil {
		log.Error("wait auxproxy response IP %s error:%+v", IP, err)
		return err
	}
	log.Info("clean up vm[%s] process success", t.vmId)
	return nil
}

func StartCleanUp(IP string, log *logger.FMLogger, maxRetry int) error {
	url := fmt.Sprintf(startCleanUp, IP)
	for retry := 0; retry < maxRetry; retry++ {
		code, err := DoCleanup(url)
		if err != nil {
			log.Error("fail to post cleanup signal to auxproxy %s with error:%+v, retry %d time", IP, err, retry)
			time.Sleep(sleepSecond * time.Second)
			continue
		}
		if code == shutdownCode {
			log.Info("success post cleanup signal to auxproxy")
			return nil
		} else {
			log.Info(" fail to get shutdown code ,retry %d, resp_code:[%s] ", retry, code)
			time.Sleep(sleepSecond * time.Second)
		}
	}
	return fmt.Errorf("fail to shutdown auxproxy after %d retry", maxRetry)
}

func WaitAuxproxy(IP string, log *logger.FMLogger, maxRetry int) error {
	url := fmt.Sprintf(shutdownStatePattern, IP)
	for retry := 0; retry < maxRetry; retry++ {
		buf, err := getCleanUpState(url)
		if err != nil {
			log.Error("fail to get auxproxy %s state with error:%+v, retry next time", IP, err)
			time.Sleep(sleepSecond * time.Second)
			continue
		}
		if buf == shutdownStateFinished {
			log.Info("success get auxproxy shutdown state")
			return nil
		} else {
			log.Info(" auxproxy shutdown is not complete ,retry %d, resp:[%s] ", retry, buf)
			time.Sleep(sleepSecond * time.Second)
		}
	}
	log.Error("failed to get auxproxy state after retry %d times, will force shutdown", maxRetry)
	return nil
}

func getCleanUpState(url string) (string, error) {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
		DisableKeepAlives: true,
	}
	client := &http.Client{Transport: tr}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", fmt.Errorf("failed to init http request with error: %s", err.Error())
	}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("failed to do request with error: %s", err.Error())
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 300 {
		return "", fmt.Errorf("failed to do request with state code: %d", resp.StatusCode)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	state := CleanUpState{}
	if err := json.Unmarshal(body, &state); err != nil {
		return "", err
	}
	return state.State, nil
}

func DoCleanup(url string) (int, error) {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
		DisableKeepAlives: true,
	}
	client := &http.Client{Transport: tr}
	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return 0, fmt.Errorf("failed to init http request with error: %s", err.Error())
	}
	resp, err := client.Do(req)
	if err != nil {
		return 0, fmt.Errorf("failed to do request with error: %s", err.Error())
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 {
		return 0, fmt.Errorf("failed to do request with state code: %d", resp.StatusCode)
	}

	return resp.StatusCode, nil
}
